#include "tasks.h"

/*-----------------------------------------------------------------------------
 *  Inicializa as variáveis globais
 *-----------------------------------------------------------------------------*/
void initVars(int *argc, char **argv[]);

/*-----------------------------------------------------------------------------
 *  Deleta as variáveis globais
 *-----------------------------------------------------------------------------*/
void deleteVars();

/*-----------------------------------------------------------------------------
 *  Função que obtém o rank e calcula os offsets para o Parser
 *-----------------------------------------------------------------------------*/
void calculateOffset();

/*-----------------------------------------------------------------------------
 *  Imprime os palindromos na tela
 *-----------------------------------------------------------------------------*/
void printPalindromes();

/*-----------------------------------------------------------------------------
 *  Obtém os palíndromos dos nós escravos
 *-----------------------------------------------------------------------------*/
void retrievePalindromes();

/*-----------------------------------------------------------------------------
 *  Obtém as informações de tempo e de quantidade
 *-----------------------------------------------------------------------------*/
void retrieveInformations();


#ifdef _USING_MPI
    /* Tempo inicial MPI */
    double startTime = MPI_Wtime();
#else
    /* Cronômetro */
    Chronometer c;
#endif

/*
 * ===  FUNCTION  ======================================================================
 *         Name:  main
 *  Description:  Função principal
 * =====================================================================================
 */
int main(int argc, char *argv[])
{
    initVars(&argc, &argv);

    /* Loop executado enquanto não terminar o verificador de palavras e frases (último) */
    for(uint i = 0; !isWPalinsFinished || !isPPalinsFinished; i++)
    {
#ifdef _USING_MP
        /* Inicia modo concorrente */
        #pragma omp parallel sections num_threads(2)
#endif
        {
#ifdef _USING_MP
            /* Seção 1 */
            #pragma omp section
#endif
            {
                /* Lê bloco do arquivo de entrada */
                readFromInputFile(i);
                /* Verifica por palíndromos e, por sua vez, por primos (2 iterações atrasadas) */
                checkForPalindromes(i);
            }

#ifdef _USING_MP
            /* Seção 2 */
            #pragma omp section
#endif
            {
                /* Separa em palavras e frases (1 iteração atrasada) */
                tokenizeBlock(i);
            }
        }

        /* Seta o flag de término do parser de palavras e de frases */
        isWParserFinished = wparser->isFinished();
        if(mode == SMALLER_TEXT)
            isPParserFinished = pparser->isFinished();
    }

    printPalindromes();
    retrieveInformations();

    deleteVars();

    return EXIT_SUCCESS;
}

/*
 * ===  FUNCTION  ======================================================================
 *         Name:  initVars
 *  Description:  Inicializa as variáveis globais
 * =====================================================================================
 */
void initVars(int *argc, char **argv[])
{

#ifdef _USING_MPI
    if(MPI_Init(argc, argv) != MPI_SUCCESS)
        exit(EXIT_FAILURE);

    MPI_Comm_size(MPI_COMM_WORLD, &tasks);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
#else
    rank = 0;
    tasks = 1;
#endif

    /* É necessário passar dois argumentos: arquivo de entrada e o modo */
    if(*argc != 3)
    {
        if(rank == 0)
            cerr << "Usage mode: " << (*argv)[0] << " <input filename> <mode: 1-bigger text 2-smaller text>" << endl;
#ifdef _USING_MPI
        MPI_Finalize();
#endif
        exit(EXIT_FAILURE);
    }

    /* Verifica se o arquivo passado existe */
    if(!Reader::fileExists((*argv)[1]))
    {
        if(rank == 0)
            cerr << "Error: File " << (*argv)[1] << " does not exists!" << endl;
#ifdef _USING_MPI
        MPI_Finalize();
#endif
        exit(EXIT_FAILURE);
    }

    /* Recupera o modo */
    mode = atoi((*argv)[2]);

    /* Recupera tamanho do arquivo */
    length = Reader::getLength((*argv)[1]);

    /* Inicializa as variáveis relacionadas ao MPI */
    calculateOffset();

    /* Inicializa o leitor */
    reader = new Reader((*argv)[1], offset);

    /* Inicializa o parser e o verificador de palíndromos para palavras */
    wparser = new Parser(offset, size, WORD);
    wputil = new PalindromeUtil(mode == BIGGER_TEXT);

    /* Inicia os flags de término para palavras como falsos */
    isWParserFinished = false;
    isWPalinsFinished = false;

    /* Caso o modo seja para texto menor */
    if(mode == SMALLER_TEXT)
    {
        /* Inicializa o parser e o verificador de palíndromos para frases */
        pparser = new Parser(offset, size, PHRASE);
        pputil = new PalindromeUtil(false);

        /* Inicia os flags de término para frases como falsos */
        isPParserFinished = false;
        isPPalinsFinished = false;
    }
    /* Caso o modo seja para o texto menor */
    else
    {
        /* Inicia os flags de término para frases como verdadeiros */
        isPParserFinished = true;
        isPPalinsFinished = true;

        /* Constrói o crivo de Eratóstenes */
        sieve = new ErathostenesSieve();
    }

    /* Inicializa os tempos */
    readTime = 0;
    parserTime = 0;
    palinTime = 0;
    primeTime = 0;
}

/*
 * ===  FUNCTION  ======================================================================
 *         Name:  deleteVars
 *  Description:  Deleta as variáveis globais
 * =====================================================================================
 */
void deleteVars()
{
    /* Deleta o parser e o verificador de palavras */
    delete wputil;
    delete wparser;

    /* Se o modo for de texto menor */
    if(mode == SMALLER_TEXT)
    {
        /* Deleta o parser e o verificador de frases */
        delete pputil;
        delete pparser;
    }
    else
    {
        /* Deleta o crivo */
        delete sieve;
    }

    /* Deleta o leitor */
    delete reader;

#ifdef _USING_MPI
    MPI_Finalize();
#endif
}

/*
 * ===  FUNCTION  ======================================================================
 *         Name:  printPalindromes
 *  Description:  Imprime os palíndromos na tela
 * =====================================================================================
 */
void printPalindromes()
{
#ifdef _USING_MPI
    retrievePalindromes();
#endif

    if(rank == 0)
    {
        /* Imprime as palavras palíndromas */
        cout << "#== Palindrome words ========================================#" << endl << endl;
        cout << *wputil;

        /* Se for o texto menor, imprime as frases palíndromas */
        if(mode == SMALLER_TEXT)
        {
            cout << endl << "#== Palindrome phrases ======================================#" << endl << endl;
            cout << *pputil;
        }
    }
}

/*
 * ===  FUNCTION  ======================================================================
 *         Name:  calculateOffset
 *  Description:  Função que obtém o rank e calcula os offsets para o Parser
 * =====================================================================================
 */
void calculateOffset()
{
    char computerName[MPI_MAX_PROCESSOR_NAME];

#ifdef _USING_MPI
    int nameSize;
    MPI_Get_processor_name(computerName, &nameSize);
#else
    strcpy(computerName, "localhost");
#endif

    /* Quantidade aproximada de bytes lidas por cada nó */
    size = length / tasks;

    /* Parte do arquivo para cada nó */
    offset = rank * size;
    ulong end = (rank + 1) * size;

    /* Por trabalhar com int, os casts anteriores podem fazer faltar bytes a serem lidos,
     que serão lidos pelo ultimo nó */
    if(rank == tasks - 1)
        end = length;

    /* Tamanho em bytes da leitura */
    size = end - offset;

    cout << "I'm " << computerName << ", rank " << (rank + 1) << " of " << tasks;
    cout << " tasks and i'm going to read from offset " << offset << ", " << size << " bytes";
    cout << endl;
}

/*
 * ===  FUNCTION  ======================================================================
 *         Name:  retrieveInformations
 *  Description:  Obtém informações de tempo e de quantidade
 * =====================================================================================
 */
void retrieveInformations()
{
    double totalReadTime, totalParserTime, totalPalinTime, totalPrimeTime;

#ifdef _USING_MPI
    /* Soma os tempos de leitura de cada máquina */
    MPI_Reduce(&readTime, &totalReadTime, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
    /* Soma os tempos de parseamento de cada máquina */
    MPI_Reduce(&parserTime, &totalParserTime, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
    /* Soma os tempos de verificação de palíndromos de cada máquina */
    MPI_Reduce(&palinTime, &totalPalinTime, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
    /* Soma os tempos de verificação de primos de cada máquina */
    MPI_Reduce(&primeTime, &totalPrimeTime, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
#else
    totalReadTime = readTime;
    totalParserTime = parserTime;
    totalPalinTime = palinTime;
    totalPrimeTime = primeTime;
#endif

    /* Imprime os resultados encontrados */
    if(rank == 0)
    {
        cout << endl << "#== Times ===================================================#" << endl << endl;
        cout << "Read time = " << totalReadTime << " miliseconds" << endl;
        cout << "Parser time = " << totalParserTime << " miliseconds" << endl;
        cout << "Palindromes time = " << totalPalinTime << " miliseconds" << endl;
        cout << "Primes time = " << totalPrimeTime << " miliseconds" << endl << endl;

        cout << "Number of Palindrome Words = " << wputil->getSize() << endl;

        if(mode == SMALLER_TEXT)
            cout << "Number of Palindrome Phrases = " << pputil->getSize() << endl;
        else
            cout << "Number of Prime Palindrome Words = " << wputil->getNPrimes() << endl << endl;

#ifdef _USING_MPI
        cout << "EXECUTION TIME = " << (MPI_Wtime() - startTime) * 1000 << " miliseconds" << endl;
#else
        cout << "EXECUTION TIME = " << c.getMiliseconds() << " miliseconds" << endl;
#endif
    }
}

/*
 * ===  FUNCTION  ======================================================================
 *         Name:  retrievePalindromes
 *  Description:  Obtém palíndromos dos nós escravos
 * =====================================================================================
 */
void retrievePalindromes()
{
    /* Se for o nó escravo */
    if(rank != 0)
    {
        /* Envia as palavras palíndromas */
        wputil->sendTo(0);

        /* Envia as frases palíndromas no caso em que for o texto menor */
        if(mode == SMALLER_TEXT)
            pputil->sendTo(0);
    }
    /* Se for nó mestre */
    else
    {
        /* Recebe todas as palavras palíndromas dos nós escravos */
        for(int n = 1; n < tasks; n++)
            wputil->receiveFrom(n);

        /* Recebe todas as frases palíndromas dos nós escravos, quando for o texto menor */
        if(mode == SMALLER_TEXT)
            for(int n = 1; n < tasks; n++)
                pputil->receiveFrom(n);
    }
}
